package com.mybatis;

import com.dq.User;

import java.lang.reflect.*;
import java.sql.*;
import java.util.*;

/**
 * @ClassName MapperProxyFactory
 * @Description 代理工厂 返回Mapper层的代理
 * @Author DuX1aoQiang
 * @Date 2023/5/22 11:06
 * @Version 1.0
 */
public class MapperProxyFactory {

    private final static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>(); //类型处理器

    static {
        typeHandlerMap.put(String.class, new StringTypeHandler());
        typeHandlerMap.put(Integer.class, new IntegerTypeHandler());

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T getMapper(Class<T> mapper) {

        Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //解析sql --> 执行sql  --> 结果处理
                //JDBC也能ok 为啥要用Mybatis relationship
                Connection connection = getConnection();
                Select annotation = method.getAnnotation(Select.class);
                String sql = annotation.value();  //#{username}  -> ? 占位

                //2.1 -> 映射参数Map   key : value
                HashMap<String, Object> k2vMap = new HashMap<>();
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    String name = parameter.getAnnotation(Param.class).value();
                    k2vMap.put(parameter.getName(), args[i]);
                    k2vMap.put(name, args[i]);
                }

                ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
                GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);
                sql = tokenParser.parse(sql);
                List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

                //2. 如何匹配参数
                PreparedStatement statement = connection.prepareStatement(sql);

//              //3. 灵活传入参数   statement.setString(1, args[0]);  //#{name}
                for (int i = 0; i < parameterMappings.size(); i++) {
                    String property = parameterMappings.get(i).getProperty();
                    Object value = k2vMap.get(property);
                    Class<?> clazzType = value.getClass();
                    typeHandlerMap.get(clazzType).setParameter(statement, i + 1, value);
                }

                statement.execute();

                // todo 类型写死了  User -> Obj
                Object obj = null;
                ArrayList<Object> list = new ArrayList<>();
                Class resultClassType = null;
                Type returnType = method.getGenericReturnType();
                if (returnType instanceof Class) {
                    resultClassType = (Class) returnType;
                } else if (returnType instanceof ParameterizedType) {
                    //泛型
                    Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
                    resultClassType = (Class) actualTypeArguments[0];
                }


//                ArrayList<User> users = new ArrayList<>();
                //获取反射Class
                ResultSet result = statement.getResultSet();

                //在while遍历result的前提下 我们需要获取用户需要查的数据 例如*  id 等
                ResultSetMetaData metaData = result.getMetaData();
                List<String> colList = new ArrayList<>(); //sql 查询所有的字段名称

                //1->
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    colList.add(metaData.getColumnName(i + 1));
                }

                //3-> 记录字段对应的set方法
                HashMap<String, Method> setterMethodMapping = new HashMap<>();
                for (Method declaredMethod : resultClassType.getDeclaredMethods()) {
                    if (declaredMethod.getName().startsWith("set")) {
                        String propertyName = declaredMethod.getName().substring(3); //set 后面的属性
                        propertyName = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1);
                        setterMethodMapping.put(propertyName, declaredMethod);
                    }
                }

                //2->
                while (result.next()) {
                    // User user = new User(result.getInt("id"), result.getString("name"), result.getInt("age"));
                    // users.add(user);
                    //todo 如何调用set方法
                    Object instance = resultClassType.newInstance(); //前提需要无参构造
                    for (int i = 0; i < colList.size(); i++) {  //填入属性内容
                        String col = colList.get(i); //id  name  age
                        Method setterMethod = setterMethodMapping.get(col);
                        Class<?> setterMethodParameterType = setterMethod.getParameterTypes()[0];  //从set方法中获取Clazz
                        TypeHandler typeHandler = typeHandlerMap.get(setterMethodParameterType);
                        setterMethod.invoke(instance, typeHandler.getResult(result, col)); //传入的值  我如何获取  result.getInt("id")
                    }
                    list.add(instance);
                }

                if (method.getReturnType().equals(List.class)) {
                    obj = list;
                } else {
                    obj = list.get(0);
                }

                //5. 关闭连接
                connection.close();
                return obj;
//                return users;
            }

            /**
             * 获取Connect对象
             * @return
             * @throws SQLException
             */
            private Connection getConnection() throws SQLException {
                Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/sys?characterEncoding=utf-8", "root", "123456");
                return connection;
            }
        });
        return (T) proxyInstance;
    }
}
